#include<stdio.h>
#include<windows.h>
#include<string.h>
#include<math.h>
#include<stdbool.h>
BITMAPFILEHEADER header;// 定义文件头信息
BITMAPINFOHEADER info;// 定义位图信息头
RGBQUAD rgb[256];// 定义彩色表
unsigned char *data;// 定义位图数据指针
int height,width;//图像高度，宽度
int string_to_num(char* str, int size);//将字符串转换成数字
int get_pixel(int x, int y);//获取(x,y)位置的像素
void set_pixel(int x, int y, int value);//设置(x,y)位置的像素灰度值
bool Read_Bmp_File(char* path);
bool Write_Bmp_File(char* path);
void Grayscale(int para,int a,int b);//灰度变换
void Histogram();//直方图均衡化
void Smooth(int para);//窗口平滑
int main(int argc,char** argv)//接受命令行参数
{
	int mode=0;//1为灰度变换，2为均衡化，3为图像平滑，如果输入不合法就是0 
	int para=0;//附加参数，当执行灰度变换时，0表示线性变换，1表示对数变换；当执行平滑操作时，0表示3x3窗口，1表示5x5窗口
    int a,b;//灰度变换的参数
	char filepath[MAX_PATH],newfilepath[MAX_PATH];//读取，存储的文件路径，常量MAX_PATH为Windows允许的最大路径长度
	if(argc==1)//没有指定参数,argv[0]为程序所在路径
	{
		printf("未指定参数。\n");
		system("pause");//暂停控制台窗口
		return 0;
	}
	if(strcmp(argv[1],"-g")==0)
	{
		printf("进行灰度变换...\n");
		mode=1;
	}
	else if(strcmp(argv[1],"-h")==0)
	{
		printf("进行直方图均衡化...\n");
		mode=2;
	}
	else if(strcmp(argv[1],"-s")==0)
	{
		printf("进行平滑操作...\n");
		mode=3;
	}
	else//非法参数
	{
		printf("[Warning] 未知的参数 - \"%s\"\n",argv[1]);
		system("pause");
		return 0;
	}
	if(mode==1)//灰度变换
	{
		if(strcmp(argv[2],"-l")==0)
		{
			printf("进行线性变换...\n");
			para=0;
		}
		else if(strcmp(argv[2],"-e")==0)
		{
			printf("进行指数变换...\n");
			para=1;
		}
		else
		{
			printf("[Warning] 未知的灰度变换参数 - \"%s\"\n",argv[2]);
			system("pause");
			return 0;
		}
		a=string_to_num(argv[3],strlen(argv[3]));//字符串数据转换成整数数据
		b=string_to_num(argv[4],strlen(argv[4]));
		if(a==-1||b==-1)
		{
			printf("非法的数字！\n");
			system("pause");
			return 0;
		}
        strcpy(filepath,argv[5]);//将argv的路径参数复制到filepath
        strcpy(newfilepath,argv[6]);
	}
	else if(mode==2)//均衡化
	{
        strcpy(filepath,argv[2]);//将argv的路径参数复制到filepath
        strcpy(newfilepath,argv[3]);
	}
	else if(mode==3)//平滑操作
	{
		if(strcmp(argv[3],"-3")==0)
		{
			printf("进行3x3窗口平滑...\n");
			para=0;
		}
		else if(strcmp(argv[3],"-5")==0)
		{
			printf("进行5x5窗口平滑...\n");
			para=1;
		}
		else
		{
			printf("[Warning] 未知的窗口平滑参数 - \"%s\"\n",argv[3]);
			system("pause");
			return 0;
		}
        strcpy(filepath,argv[2]);//将argv的路径参数复制到filepath
        strcpy(newfilepath,argv[4]);
	}
	if(!Read_Bmp_File(filepath))
	{
		printf("读取文件失败\n");
		system("pause");
		return 0;
	}
	if(mode==1)
	{
		Grayscale(para,a,b);
	}
	else if(mode==2)
	{
		Histogram();
	}
	else
	{
		Smooth(para);
	}

	if(!Write_Bmp_File(newfilepath))
	{
		printf("写入文件失败\n");
		system("pause");
		return 0;
	}
	printf("写入文件成功\n");
	system("pause");
	return 0;
}



bool Read_Bmp_File(char* path)
{
	FILE* file = fopen(path, "rb");
	printf("读取文件\"%s\"...\n",path);
	if(file==NULL)
	{
		return false;//读取失败
	}
	fread(&header, sizeof(BITMAPFILEHEADER), 1, file);// 读取文件信息头
	fread(&info, sizeof(BITMAPINFOHEADER), 1, file);// 读取位图信息头
	fread(&rgb, sizeof(RGBQUAD), 256, file);// 读取彩色表
	width=info.biWidth;
	height=info.biHeight;
	data = (unsigned char*)malloc(width*height*sizeof(char));// 动态分配位图数据内存
	fread(data, sizeof(unsigned char), width*height, file);// 读取元素的位图数据
	fclose(file);// 关闭图片
	return true;
}

bool Write_Bmp_File(char* path)
{
	FILE* file = fopen(path, "wb");
	printf("写入文件\"%s\"...\n",path);
	if(file==NULL)
	{
		return false;//写入失败
	}
	fwrite(&header, sizeof(BITMAPFILEHEADER), 1, file);		//写文件头
	fwrite(&info, sizeof(BITMAPINFOHEADER), 1, file);		//写信息头
	fwrite(&rgb, sizeof(RGBQUAD), 256, file);				//写调色板
	fwrite(data, sizeof(unsigned char), width*height, file);		            //写颜色数据
	fclose(file);
	free(data);
	return true;
}
int string_to_num(char* str, int size)
{
	int n=0;
	for(int i=0;i<size;i++)
	{
		if(!(str[i]>='0'&&str[i]<='9'))//非法字符
		{
			return -1;
		}
		n*=10;
		n+=str[i]-'0';
	}
	return n;
}

int get_pixel(int x, int y)
{
	int val;
	val=data[y+x*width];
	return val;
}
void set_pixel(int x, int y, int value)
{
	unsigned char val=(unsigned char)value;
	data[y+x*width]=val;
	return;
}
void Grayscale(int para,int a,int b)
{
	printf("设置参数：a=%d, b=%d\n",a,b);
	if(para==0)
	{
		double a_=255.0,b_=0.0;//原来的a,b值
		for(int i=0;i<height;i++)
		{
			for(int j=0;j<width;j++)
			{
				double x=get_pixel(i,j);
				a_=x<a_?x:a_;
				b_=x>b_?x:b_;
				//set_pixel(i,j,255-x);
			}
		}
		for(int i=0;i<height;i++)
		{
			for(int j=0;j<width;j++)
			{
				double f_ij=get_pixel(i,j);
				double g_ij=a+(b-a)*(f_ij-a_)/(b_-a_);
				set_pixel(i,j,g_ij);
			}
		}
		return;//线性变换
	}
	else
	{
		double min_pix=255.0,max_pix=0.0;//最小，最大像素值
		for(int i=0;i<height;i++)
		{
			for(int j=0;j<width;j++)
			{
				double x=get_pixel(i,j);
				min_pix=x<min_pix?x:min_pix;
				max_pix=x>max_pix?x:max_pix;
			}
		}
		double c=(b-a)/(log(1.0+max_pix/255.0)-log(1.0+min_pix/255.0));
		for(int i=0;i<height;i++)
		{
			for(int j=0;j<width;j++)
			{
				double f_ij=get_pixel(i,j);
				double g_ij=log(f_ij/255.0+1.0)*c;
				int res=g_ij;
				//printf("%.6lf ",g_ij);
				set_pixel(i,j,res);
			}
		}
		return;//指数变换
	}
}
void Histogram()//直方图均衡化
{
	int freq[256]={0};//直方图
	for(int i=0;i<height;i++)
	{
		for(int j=0;j<width;j++)
		{
			int x=get_pixel(i,j);
			freq[x]++;//统计每个灰度频率
		}
	}
	double accumlate_func[256];//累计灰度直方图函数，取值为(0,1)，accumlate_func[i]表示灰度<=i的像素占总像素的比值
	double sum=0.0;
	for(int i=0;i<256;i++)
	{
		sum+=freq[i];//累计灰度值<=i的像素个数
		accumlate_func[i]=sum/height/width;//这些像素占总像素的比值
		//printf("%.6lf ",accumlate_func[i]*255);
	}
	for(int i=0;i<height;i++)
	{
		for(int j=0;j<width;j++)
		{
			int r_k=get_pixel(i,j);
			double s_k=accumlate_func[r_k]*255.0;//函数取值为(0,1),乘以255转换为灰度值
			set_pixel(i,j,s_k);
		}
	}
	return;
}
void Smooth(int para)//窗口平滑
{
	int **buffer;
	buffer=(int**)malloc(height*sizeof(int*));
	for(int i=0;i<height;i++)
	{
		buffer[i]=(int*)malloc(width*sizeof(int));
	}//动态分配malloc[height][width],储存计算结果
	int size=0;
	if(para==0)
	{
		size=3;//3x3
	}
	else
	{
		size=5;//5x5
	}
	int max_offset=(size-1)/2;//距离中心像素的最大偏移量
	for(int m=0;m<height;m++)
	{
		for(int n=0;n<width;n++)
		{
			double cnt=size*size,sum=0;//cnt表示合法像素的个数
			for(int d1=-max_offset;d1<=max_offset;d1++)//横坐标偏移量，取值0,1,2
			{
				for(int d2=-max_offset;d2<=max_offset;d2++)//纵坐标偏移量
				{
					if(m+d1<0||m+d1>=height||n+d2<0||n+d2>=width)//出界
					{
						cnt--;
						continue;//不执行循环后面的代码
					}
					sum+=get_pixel(m+d1,n+d2);
				}
			}
			buffer[m][n]=sum/cnt;
		}
	}for(int m=0;m<height;m++)
	{
		for(int n=0;n<width;n++)
		{
			set_pixel(m,n,buffer[m][n]);
		}
	}
	for(int i=0;i<height;i++)
	{
		free(buffer[i]);//释放使用的缓存数组free
	}
	free(buffer);
	return;
}